// import com.alibaba.druid.sql.visitor.SQLASTVisitor;

// import java.util.ArrayList;
// import java.util.List;

#include "SQLOver.h"
#include "../visitor/SQLASTVisitor.h"
#include "../../utils/StringUtils.h"
#include "../../utils/ifeq.h"
#include "SQLOrderBy.h"
#include "SQLName.h"

SQLOver_WindowingType_ptr SQLOver_WindowingType::ROWS = SQLOver_WindowingType_ptr(new SQLOver_WindowingType(1ULL << 0, make_string_ptr("ROWS")));
SQLOver_WindowingType_ptr SQLOver_WindowingType::RANGE = SQLOver_WindowingType_ptr(new SQLOver_WindowingType(1ULL << 1, make_string_ptr("RANGE")));

SQLOver_WindowingType::SQLOver_WindowingType(uint64_t mask, string_ptr name)
{
  this->mask = mask;
  this->name = name;
  this->nameLCase = StringUtils::toLower(name);
}

SQLOver_WindowingBound_ptr SQLOver_WindowingBound::UNBOUNDED_PRECEDING = SQLOver_WindowingBound_ptr(new SQLOver_WindowingBound(1ULL << 0, make_string_ptr("UNBOUNDED PRECEDING")));
SQLOver_WindowingBound_ptr SQLOver_WindowingBound::PRECEDING = SQLOver_WindowingBound_ptr(new SQLOver_WindowingBound(1ULL << 1, make_string_ptr("PRECEDING")));
SQLOver_WindowingBound_ptr SQLOver_WindowingBound::CURRENT_ROW = SQLOver_WindowingBound_ptr(new SQLOver_WindowingBound(1ULL << 2, make_string_ptr("CURRENT ROW")));
SQLOver_WindowingBound_ptr SQLOver_WindowingBound::FOLLOWING = SQLOver_WindowingBound_ptr(new SQLOver_WindowingBound(1ULL << 3, make_string_ptr("FOLLOWING")));
SQLOver_WindowingBound_ptr SQLOver_WindowingBound::UNBOUNDED_FOLLOWING = SQLOver_WindowingBound_ptr(new SQLOver_WindowingBound(1ULL << 4, make_string_ptr("UNBOUNDED FOLLOWING")));

SQLOver_WindowingBound::SQLOver_WindowingBound(uint64_t mask, string_ptr name)
{
  this->mask = mask;
  this->name = name;
  this->nameLCase = StringUtils::toLower(name);
}

SQLOver::SQLOver()
{
  partitionBy = std::make_shared<std::list<SQLExpr_ptr>>();
}

SQLOver::SQLOver(SQLOrderBy_ptr orderBy)
{
  partitionBy = std::make_shared<std::list<SQLExpr_ptr>>();
  this->setOrderBy(orderBy);
}

// @Override
void SQLOver::accept0(SQLASTVisitor_ptr visitor)
{
  if (visitor->visit(SharedObject(SQLOver)))
  {
    if (partitionBy != nullptr)
    {
      for (SQLExpr_ptr item : *partitionBy)
      {
        if (item != nullptr)
        {
          item->accept(visitor);
        }
      }
    }

    if (orderBy != nullptr)
    {
      orderBy->accept(visitor);
    }

    if (distributeBy != nullptr)
    {
      distributeBy->accept(visitor);
    }

    if (sortBy != nullptr)
    {
      sortBy->accept(visitor);
    }

    if (clusterBy != nullptr)
    {
      clusterBy->accept(visitor);
    }

    if (of != nullptr)
    {
      of->accept(visitor);
    }
  }
  visitor->endVisit(SharedObject(SQLOver));
}

SQLOrderBy_ptr SQLOver::getOrderBy()
{
  return orderBy;
}

void SQLOver::setOrderBy(SQLOrderBy_ptr x)
{
  if (x != nullptr)
  {
    x->setParent(SharedObject(SQLOver));
  }
  this->orderBy = x;
}

SQLOrderBy_ptr SQLOver::getClusterBy()
{
  return clusterBy;
}

void SQLOver::setClusterBy(SQLOrderBy_ptr x)
{
  if (x != nullptr)
  {
    x->setParent(SharedObject(SQLOver));
  }
  this->clusterBy = x;
}

SQLOrderBy_ptr SQLOver::getDistributeBy()
{
  return distributeBy;
}

void SQLOver::setDistributeBy(SQLOrderBy_ptr x)
{
  if (x != nullptr)
  {
    x->setParent(SharedObject(SQLOver));
  }
  this->distributeBy = x;
}

SQLOrderBy_ptr SQLOver::getSortBy()
{
  return sortBy;
}

void SQLOver::setSortBy(SQLOrderBy_ptr x)
{
  if (x != nullptr)
  {
    x->setParent(SharedObject(SQLOver));
  }
  this->sortBy = x;
}

SQLName_ptr SQLOver::getOf()
{
  return of;
}

void SQLOver::setOf(SQLName_ptr of)
{
  if (of != nullptr)
  {
    of->setParent(SharedObject(SQLOver));
  }
  this->of = of;
}

SQLExpr_list_ptr SQLOver::getPartitionBy()
{
  return partitionBy;
}

SQLOver_WindowingType_ptr SQLOver::getWindowingType()
{
  return windowingType;
}

void SQLOver::setWindowingType(SQLOver_WindowingType_ptr windowingType)
{
  this->windowingType = windowingType;
}

BOOL_ptr SQLOver::isWindowingPreceding()
{
  return windowingPreceding;
}

void SQLOver::setWindowingPreceding(BOOL_ptr windowingPreceding)
{
  this->windowingPreceding = windowingPreceding;
}

SQLExpr_ptr SQLOver::getWindowingBetweenBegin()
{
  return windowingBetweenBegin;
}

void SQLOver::setWindowingBetweenBegin(SQLExpr_ptr windowingBetweenBegin)
{
  this->windowingBetweenBegin = windowingBetweenBegin;
}

SQLExpr_ptr SQLOver::getWindowingBetweenEnd()
{
  return windowingBetweenEnd;
}

void SQLOver::setWindowingBetweenEnd(SQLExpr_ptr windowingBetweenEnd)
{
  this->windowingBetweenEnd = windowingBetweenEnd;
}

BOOL_ptr SQLOver::isWindowingBetweenEndPreceding()
{
  return (windowingBetweenEndBound == SQLOver_WindowingBound::PRECEDING)?BOOL::TRUE:BOOL::FALSE;
}

BOOL_ptr SQLOver::isWindowingBetweenEndFollowing()
{
  return (windowingBetweenEndBound == SQLOver_WindowingBound::FOLLOWING)?BOOL::TRUE:BOOL::FALSE;
}

SQLOver_WindowingBound_ptr SQLOver::getWindowingBetweenBeginBound()
{
  return windowingBetweenBeginBound;
}

void SQLOver::setWindowingBetweenBeginBound(SQLOver_WindowingBound_ptr windowingBetweenBeginBound)
{
  this->windowingBetweenBeginBound = windowingBetweenBeginBound;
}

SQLOver_WindowingBound_ptr SQLOver::getWindowingBetweenEndBound()
{
  return windowingBetweenEndBound;
}

void SQLOver::setWindowingBetweenEndBound(SQLOver_WindowingBound_ptr windowingBetweenEndBound)
{
  this->windowingBetweenEndBound = windowingBetweenEndBound;
}

BOOL_ptr SQLOver::isExcludeCurrentRow()
{
  return excludeCurrentRow;
}

void SQLOver::setExcludeCurrentRow(BOOL_ptr excludeCurrentRow)
{
  this->excludeCurrentRow = excludeCurrentRow;
}

// @Override
bool SQLOver::equals(Object_ptr o)
{
  if (this == o.get())
  {
    return true;
  }
  if (o == nullptr || getClass()->c_str() != o->getClass()->c_str())
  {
    return false;
  }

  SQLOver_ptr sqlOver = std::dynamic_pointer_cast<SQLOver>(o);

  if (windowingPreceding != sqlOver->windowingPreceding)
  {
    return false;
  }
  if (windowingFollowing != sqlOver->windowingFollowing)
  {
    return false;
  }
  if (!IFEQ::ifeq<SQLExpr_ptr>(partitionBy, sqlOver->partitionBy))
  {
    return false;
  }
  if (orderBy != nullptr ? !orderBy->equals(sqlOver->orderBy) : sqlOver->orderBy != nullptr)
  {
    return false;
  }
  if (of != nullptr ? !of->equals(sqlOver->of) : sqlOver->of != nullptr)
  {
    return false;
  }
  if (windowingType != sqlOver->windowingType)
  {
    return false;
  }
  if (windowingBetweenBegin != nullptr ? !windowingBetweenBegin->equals(sqlOver->windowingBetweenBegin) : sqlOver->windowingBetweenBegin != nullptr)
  {
    return false;
  }
  if (windowingBetweenBeginBound != sqlOver->windowingBetweenBeginBound)
  {
    return false;
  }
  if (windowingBetweenEnd != nullptr ? !windowingBetweenEnd->equals(sqlOver->windowingBetweenEnd) : sqlOver->windowingBetweenEnd != nullptr)
  {
    return false;
  }
  return windowingBetweenEndBound == sqlOver->windowingBetweenEndBound;
}

// @Override
int SQLOver::hashCode()
{
  int result = partitionBy != nullptr ? typeid(*partitionBy).hash_code() /*partitionBy->hashCode()*/ : 0;
  result = 31 * result + (orderBy != nullptr ? orderBy->hashCode() : 0);
  result = 31 * result + (of != nullptr ? of->hashCode() : 0);
  result = 31 * result + (windowingType != nullptr ? typeid(*windowingType).hash_code() /*windowingType->hashCode()*/ : 0);
  result = 31 * result + (windowingPreceding ? 1 : 0);
  result = 31 * result + (windowingFollowing ? 1 : 0);
  result = 31 * result + (windowingBetweenBegin != nullptr ? windowingBetweenBegin->hashCode() : 0);
  result = 31 * result + (windowingBetweenBeginBound != nullptr ? typeid(*windowingBetweenBeginBound).hash_code() /*windowingBetweenBeginBound->hashCode()*/ : 0);
  result = 31 * result + (windowingBetweenEnd != nullptr ? windowingBetweenEnd->hashCode() : 0);
  result = 31 * result + (windowingBetweenEndBound != nullptr ? typeid(*windowingBetweenEndBound).hash_code() /*windowingBetweenEndBound->hashCode()*/ : 0);
  return result;
}

void SQLOver::cloneTo(SQLOver_ptr x)
{
  for (SQLExpr_ptr item : *partitionBy)
  {
    SQLExpr_ptr item1 = std::dynamic_pointer_cast<SQLExpr>(item->clone());
    item1->setParent(x);
    x->partitionBy->push_back(item1);
  }

  if (orderBy != nullptr)
  {
    x->setOrderBy(std::dynamic_pointer_cast<SQLOrderBy>(orderBy->clone()));
  }

  if (of != nullptr)
  {
    x->setOf(std::dynamic_pointer_cast<SQLName>(of->clone()));
  }

  x->windowingType = windowingType;
  x->windowingPreceding = windowingPreceding;
  x->windowingFollowing = windowingFollowing;

  if (windowingBetweenBegin != nullptr)
  {
    x->setWindowingBetweenBegin(std::dynamic_pointer_cast<SQLExpr>(windowingBetweenBegin->clone()));
  }
  x->windowingBetweenBeginBound = windowingBetweenBeginBound;
  x->windowingBetweenEndBound = windowingBetweenEndBound;

  if (windowingBetweenEnd != nullptr)
  {
    x->setWindowingBetweenEnd(std::dynamic_pointer_cast<SQLExpr>(windowingBetweenEnd->clone()));
  }
}

SQLObject_ptr SQLOver::clone()
{
  SQLObject_ptr clone_tmp = std::shared_ptr<SQLOver>(new SQLOver());
  std::shared_ptr<SQLOver> x = std::dynamic_pointer_cast<SQLOver>(clone_tmp);
  // SQLOver x = new SQLOver();
  cloneTo(x);
  return x;
}

// @Override
BOOL_ptr SQLOver::replace(SQLExpr_ptr expr, SQLExpr_ptr target)
{
  if (windowingBetweenBegin == expr)
  {
    setWindowingBetweenBegin(target);
    return BOOL::TRUE;
  }

  if (windowingBetweenEnd == expr)
  {
    setWindowingBetweenEnd(target);
    return BOOL::TRUE;
  }

  auto it_partitionBy = partitionBy->begin();
  for (int i = 0; i < partitionBy->size(); i++)
  {
    std::advance(it_partitionBy, i);
    if (*it_partitionBy == expr)
    {
      *it_partitionBy = target;
      target->setParent(SharedObject(SQLOver));
    }
  }

  return BOOL::FALSE;
}
